/*
 * jQuery Textarea Input Support Plugin
 * https://github.com/mataki/jquery-textarea-imput-support
 *
 * Copyright 2011, Akihiro Matsumura
 * https://blog.mat-aki.net/
 *
 * Licensed under the MIT license:
 * http://creativecommons.org/licenses/MIT/
 */

/* global jQuery */

(function($) {
  $.widget("ui.textareainputsupport", $.ui.autocomplete.prototype);

  $.extend($.ui.textareainputsupport.prototype, {
    search: function(value, event){
      value = value != null ? value : getCaretWord(this.element).text;

      if ( value.length < this.options.minLength ) {
        return this.close( event );
      }

      clearTimeout( this.closing );
      if ( this._trigger("search") === false ) {
        return undefined;
      }

      return this._search( value );
    },
    _initSource: function() {
      var array,
          url;
      if ( $.isArray(this.options.source) ) {
        array = this.options.source;
        this.source = function( request, response ) {
          response( $.ui.textareainputsupport.filter(array, request.term) );
        };
      } else if ( typeof this.options.source === "string" ) {
        url = this.options.source;
        this.source = function( request, response ) {
          $.getJSON( url, request, response );
        };
      } else {
        this.source = this.options.source;
      }
    },
    _create: function() {
      var self = this,
          doc = this.element[ 0 ].ownerDocument;
      this.element
        .addClass( "ui-autocomplete-input" )
        .attr( "autocomplete", "off" )
      // TODO verify these actually work as intended
        .attr({
          role: "textbox",
          "aria-autocomplete": "list",
          "aria-haspopup": "true"
        })
        .bind( "keydown.autocomplete", self.keydownFunc(self))
        .bind( "focus.autocomplete", function() {
          if ( self.options.disabled ) {
            return;
          }

          self.selectedItem = null;
          self.previous = self.element.val();
        })
        .bind( "blur.autocomplete", function( event ) {
          if ( self.options.disabled ) {
            return;
          }

          clearTimeout( self.searching );
          // clicks on the menu (or a button to trigger a search) will cause a blur event
          self.closing = setTimeout(function() {
            self.close( event );
            self._change( event );
          }, 150 );
        });
      this._initSource();
      this.response = function() {
        return self._response.apply( self, arguments );
      };
      this.menu = $( "<ul></ul>" )
        .addClass( "ui-autocomplete" )
        .appendTo( $( this.options.appendTo || "body", doc )[0] )
      // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
        .mousedown(function( event ) {
          // clicking on the scrollbar causes focus to shift to the body
          // but we can't detect a mouseup or a click immediately afterward
          // so we have to track the next mousedown and close the menu if
          // the user clicks somewhere outside of the autocomplete
          var menuElement = self.menu.element[ 0 ];
          if ( event.target === menuElement ) {
            setTimeout(function() {
              $( document ).one( 'mousedown', function( event ) {
                if ( event.target !== self.element[ 0 ] &&
                     event.target !== menuElement &&
                     !$.ui.contains( menuElement, event.target ) ) {
                       self.close();
                     }
              });
            }, 1 );
          }

          // use another timeout to make sure the blur-event-handler on the input was already triggered
          setTimeout(function() {
            clearTimeout( self.closing );
          }, 13);
        })
        .menu(this.menuOpt(self, doc))
        .zIndex( this.element.zIndex() + 1 )
      // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
        .css({ top: 0, left: 0 })
        .hide()
        .data( "menu" );
      if ( $.fn.bgiframe ) {
        this.menu.element.bgiframe();
      }
    },
    keydownFunc: function(self) {
      return function(event){
        if ( self.options.disabled ) {
          return;
        }

        var keyCode = $.ui.keyCode;
        switch( event.keyCode ) {
        case keyCode.PAGE_UP:
          self._move( "previousPage", event );
          break;
        case keyCode.PAGE_DOWN:
          self._move( "nextPage", event );
          break;
        case keyCode.UP:
          self._move( "previous", event );
          // prevent moving cursor to beginning of text field in some browsers
          event.preventDefault();
          break;
        case keyCode.DOWN:
          self._move( "next", event );
          // prevent moving cursor to end of text field in some browsers
          event.preventDefault();
          break;
        case keyCode.ENTER:
        case keyCode.NUMPAD_ENTER:
          // when menu is open or has focus
          if ( self.menu.element.is( ":visible" ) ) {
            event.preventDefault();
          }
          //passthrough - ENTER and TAB both select the current element
        case keyCode.TAB:
          if ( !self.menu.active ) {
            self._move( "next", event );
          }
          self.menu.select( event );
          break;
        case keyCode.ESCAPE:
          self.element.val( self.term );
          self.close( event );
          break;
        default:
          // keypress is triggered before the input value is changed
          clearTimeout( self.searching );
          self.searching = setTimeout(function() {
            // only search if the value has changed
            if ( self.term != self.element.val() ) {
              self.selectedItem = null;
              self.search( null, event );
            }
          }, self.options.delay );
          break;
        }
      };
    },
    menuOpt: function(self, doc){
      return {
        focus: function( event, ui ) {
          var item = ui.item.data( "item.autocomplete" );
          if ( false !== self._trigger( "focus", null, { item: item } ) ) {
            // use value to match what will end up in the input, if it was a key event
            if ( /^key/.test(event.originalEvent.type) ) {
              self.element.val( item.value );
            }
          }
        },
        selected: function( event, ui ) {
          var item = ui.item.data( "item.autocomplete" ),
              previous = self.previous;

          // only trigger when focus was lost (click on menu)
          if ( self.element[0] !== doc.activeElement ) {
            self.element.focus();
            self.previous = previous;
          }

          if ( false !== self._trigger( "select", event, { item: item } ) ) {
            self.element.val( item.value );
          }

          self.close( event );
          self.selectedItem = item;
        },
        blur: function( event, ui ) {
          // don't set the value of the text field if it's already correct
          // this prevents moving the cursor unnecessarily
          if ( self.menu.element.is(":visible") &&
               ( self.element.val() !== self.term ) ) {
                 self.element.val( self.term );
               }
        }
      };
    }
  });

  $.extend( $.ui.textareainputsupport, {
    escapeRegex: function( value ) {
      return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    },
    filter: function(array, term){
      return selectHeadMatch(array, term);
    }
  });

  $.extend( $.ui.textareainputsupport.prototype.options, {
    select: function( event, ui ) {
      var curPos = getCaretWord(this);
      $(this).setSelection(curPos.start, curPos.end);
      $(this).replaceSelectedText(ui.item.value + " ");
      return false;
    },
    focus: function(event, ui){
      var oldPos = $(this).getSelection();
      var oldselect = getCaretWord(this);

      $(this).setSelection(oldselect.start, oldselect.end);
      $(this).replaceSelectedText(ui.item.value);
      $(this).setSelection(oldPos.start, oldselect.start + ui.item.value.length);
      return false;
    }
  });

  function hasPrefix(word){
    var prefixs = ["@", "+"];
    isInclude(prefixs, word[0]);
  }

  function isInclude(array, obj){
    var res = $.grep(array, function(n,i){
      return (n == obj);
    });
    return res.length > 0;
  };

  $.getPositionWord = function(text, pos){
    var start, end;
    var delimiter = [" ", "　", "\n"];

    for(var i = pos; i >= 0; i--){
      var res = isInclude(delimiter, text[i-1]);
      if(res){
        break;
      }
      start = i-1;
      start = start > 0 ? start : 0;
    }

    for(i = pos; i <= text.length; i++){
      end = i;
      if(isInclude(delimiter, text[i])){
        break;
      }
    }

    return { start: start, end: end, text: text.slice(start, end) };
  };

  function getCaretWord(obj){
    var elem = $(obj);
    var sel = elem.getSelection();
    var text = elem.val();
    var result = $.getPositionWord(text, sel.start);

    return result;
  };

  function selectHeadMatch(array, term){
    var reg = RegExp("^" + term, "i");
    return $.grep(array, function(n,i){
      return reg.test(n);
    });
  };
})(jQuery);



/**
 * @license Rangy Text Inputs, a cross-browser textarea and text input library plug-in for jQuery.
 *
 * Part of Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on jQuery 1.0 or later.
 *
 * Copyright 2010, Tim Down
 * Licensed under the MIT license.
 * Version: 0.1.205
 * Build date: 5 November 2010
 */
(function($) {
    var UNDEF = "undefined";
    var getSelection, setSelection, deleteSelectedText, deleteText, insertText;
    var replaceSelectedText, surroundSelectedText, extractSelectedText, collapseSelection;

    // Trio of isHost* functions taken from Peter Michaux's article:
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
    function isHostMethod(object, property) {
        var t = typeof object[property];
        return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";
    }

    function isHostProperty(object, property) {
        return typeof(object[property]) != UNDEF;
    }

    function isHostObject(object, property) {
        return !!(typeof(object[property]) == "object" && object[property]);
    }

    function fail(reason) {
        if (window.console && window.console.log) {
            window.console.log("TextInputs module for Rangy not supported in your browser. Reason: " + reason);
        }
    }

    function adjustOffsets(el, start, end) {
        if (start < 0) {
            start += el.value.length;
        }
        if (typeof end == UNDEF) {
            end = start;
        }
        if (end < 0) {
            end += el.value.length;
        }
        return { start: start, end: end };
    }

    function makeSelection(el, start, end) {
        return {
            start: start,
            end: end,
            length: end - start,
            text: el.value.slice(start, end)
        };
    }

    function getBody() {
        return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
    }

    $(document).ready(function() {
        var testTextArea = document.createElement("textarea");

        getBody().appendChild(testTextArea);

        if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {
            getSelection = function(el) {
                var start = el.selectionStart, end = el.selectionEnd;
                return makeSelection(el, start, end);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                el.selectionStart = offsets.start;
                el.selectionEnd = offsets.end;
            };

            collapseSelection = function(el, toStart) {
                if (toStart) {
                    el.selectionEnd = el.selectionStart;
                } else {
                    el.selectionStart = el.selectionEnd;
                }
            };
        } else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection") &&
                   isHostMethod(document.selection, "createRange")) {

            getSelection = function(el) {
                var start = 0, end = 0, normalizedValue, textInputRange, len, endRange;
                var range = document.selection.createRange();

                if (range && range.parentElement() == el) {
                    len = el.value.length;

                    normalizedValue = el.value.replace(/\r\n/g, "\n");
                    textInputRange = el.createTextRange();
                    textInputRange.moveToBookmark(range.getBookmark());
                    endRange = el.createTextRange();
                    endRange.collapse(false);
                    if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                        start = end = len;
                    } else {
                        start = -textInputRange.moveStart("character", -len);
                        start += normalizedValue.slice(0, start).split("\n").length - 1;
                        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                            end = len;
                        } else {
                            end = -textInputRange.moveEnd("character", -len);
                            end += normalizedValue.slice(0, end).split("\n").length - 1;
                        }
                    }
                }

                return makeSelection(el, start, end);
            };

            // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
            // the textarea value is two characters. This function corrects for that by converting a text offset into a
            // range character offset by subtracting one character for every line break in the textarea prior to the
            // offset
            var offsetToRangeCharacterMove = function(el, offset) {
                return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                var range = el.createTextRange();
                var startCharMove = offsetToRangeCharacterMove(el, offsets.start);
                range.collapse(true);
                if (offsets.start == offsets.end) {
                    range.move("character", startCharMove);
                } else {
                    range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));
                    range.moveStart("character", startCharMove);
                }
                range.select();
            };

            collapseSelection = function(el, toStart) {
                var range = document.selection.createRange();
                range.collapse(toStart);
                range.select();
            };
        } else {
            getBody().removeChild(testTextArea);
            fail("No means of finding text input caret position");
            return;
        }

        // Clean up
        getBody().removeChild(testTextArea);

        deleteText = function(el, start, end, moveSelection) {
            var val;
            if (start != end) {
                val = el.value;
                el.value = val.slice(0, start) + val.slice(end);
            }
            if (moveSelection) {
                setSelection(el, start, start);
            }
        };

        deleteSelectedText = function(el) {
            var sel = getSelection(el);
            deleteText(el, sel.start, sel.end, true);
        };

        extractSelectedText = function(el) {
            var sel = getSelection(el), val;
            if (sel.start != sel.end) {
                val = el.value;
                el.value = val.slice(0, sel.start) + val.slice(sel.end);
            }
            setSelection(el, sel.start, sel.start);
            return sel.text;
        };

        insertText = function(el, text, index, moveSelection) {
            var val = el.value, caretIndex;
            el.value = val.slice(0, index) + text + val.slice(index);
            if (moveSelection) {
                caretIndex = index + text.length;
                setSelection(el, caretIndex, caretIndex);
            }
        };

        replaceSelectedText = function(el, text) {
            var sel = getSelection(el), val = el.value;
            el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
            var caretIndex = sel.start + text.length;
            setSelection(el, caretIndex, caretIndex);
        };

        surroundSelectedText = function(el, before, after) {
            var sel = getSelection(el), val = el.value;

            el.value = val.slice(0, sel.start) + before + sel.text + after + val.slice(sel.end);
            var startIndex = sel.start + before.length;
            var endIndex = startIndex + sel.length;
            setSelection(el, startIndex, endIndex);
        };

        function jQuerify(func, returnThis) {
            return function() {
                var el = this.jquery ? this[0] : this;
                var nodeName = el.nodeName.toLowerCase();

                if (el.nodeType == 1 && (nodeName == "textarea" || (nodeName == "input" && el.type == "text"))) {
                    var args = [el].concat(Array.prototype.slice.call(arguments));
                    var result = func.apply(this, args);
                    if (!returnThis) {
                        return result;
                    }
                }
                if (returnThis) {
                    return this;
                }
            };
        }

        $.fn.extend({
            getSelection: jQuerify(getSelection, false),
            setSelection: jQuerify(setSelection, true),
            collapseSelection: jQuerify(collapseSelection, true),
            deleteSelectedText: jQuerify(deleteSelectedText, true),
            deleteText: jQuerify(deleteText, true),
            extractSelectedText: jQuerify(extractSelectedText, false),
            insertText: jQuerify(insertText, true),
            replaceSelectedText: jQuerify(replaceSelectedText, true),
            surroundSelectedText: jQuerify(surroundSelectedText, true)
        });
    });
})(jQuery);